package com.wlyy.bcwlw.common.aspect;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.wlyy.bcwlw.common.cache.CacheEvict;
import com.wlyy.bcwlw.common.cache.Cacheable;
import com.wlyy.bcwlw.common.cache.RedisHashService;
import com.wlyy.bcwlw.sys.config.service.StaticConfigService;


@Component
@Aspect
public class CacheAspect {
	
	private static final Logger logger =  LogManager.getLogger(CacheAspect.class);
	
	@Autowired
	private RedisHashService redisHashService;
	@Autowired
	private StaticConfigService staticConfigService;
	
	private final String CACHE_ENABLE_KEY ="CACHE_ENABLE";
	
	/**
	 * 获取被拦截方法对象
	 * 
	 * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 而缓存的注解在实现类的方法上
	 * 所以应该使用反射获取当前对象的方法对象
	 */
	private Method getMethod(ProceedingJoinPoint pjp) {
		// 获取参数的类型
		Object[] args = pjp.getArgs();
		Class[] argTypes = new Class[pjp.getArgs().length];
		for (int i = 0; i < args.length; i++) {
			argTypes[i] = args[i].getClass();
		}
		Method method = null;
		try {
			method = pjp.getTarget().getClass()
					.getMethod(pjp.getSignature().getName(), argTypes);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		return method;

	}

	/**
	 * 获取缓存的key key 定义在注解上，支持SPEL表达式
	 * 
	 * @param pjp
	 * @return
	 */
	private String parseKey(String key, Method method, Object[] args) {

		// 获取被拦截方法参数名列表(使用Spring支持类库)
		LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
		String[] paraNameArr = u.getParameterNames(method);

		// 使用SPEL进行key的解析
		ExpressionParser parser = new SpelExpressionParser();
		// SPEL上下文
		StandardEvaluationContext context = new StandardEvaluationContext();
		// 把方法参数放入SPEL上下文中
		for (int i = 0; i < paraNameArr.length; i++) {
			context.setVariable(paraNameArr[i], args[i]);
		}
		return parser.parseExpression(key).getValue(context, String.class);
	}
	
	/**
	 * 
	* @Title. getDescription
	* @Description. 获取注解里的描述说明
	* @param jpt
	* @param type
	* @return String
	* @exception.
	 */
	private List<String> getKey(JoinPoint jpt){
        String methodName = jpt.getSignature().getName();    
        Object[] arguments = jpt.getArgs();    
        Class<?> targetClass = jpt.getTarget().getClass();    
        Method[] methods = targetClass.getMethods();    
        String[] keys = new String[]{};    
         for (Method method : methods) {    
             if (method.getName().equals(methodName)) {    
                Class<?>[] clazzs = method.getParameterTypes();    
                 if (clazzs.length == arguments.length) {
                	 keys = method.getAnnotation(CacheEvict. class).key();    
                     break;    
                }    
            }    
        }    
        return Arrays.asList(keys);    
	}
	
	// Controller层切点
	@Pointcut("@annotation(com.wlyy.bcwlw.common.cache.Cacheable)")
	public void cacheableAspect() {
	}
	
	// Controller层切点
	@Pointcut("@annotation(com.wlyy.bcwlw.common.cache.CacheEvict)")
	public void cacheEvictAspect() {
	}
	
	/**
	 * 定义缓存逻辑
	 */
	@Around("cacheableAspect()")
	public Object aroundCache(ProceedingJoinPoint pjp) {
		Object result = null;
		Boolean cacheEnable =staticConfigService.getBooleanValue(CACHE_ENABLE_KEY);
//		System.err.println("缓存设置："+cacheEnable);
		// 判断是否开启缓存
		if (!cacheEnable) {
			try {
				result = pjp.proceed();
			} catch (Throwable e) {
				e.printStackTrace();
			}
			return result;
		}

		Method method = getMethod(pjp);
		Cacheable cacheable = method.getAnnotation(com.wlyy.bcwlw.common.cache.Cacheable.class);

		String key = cacheable.key();
		
		String fieldKey = parseKey(cacheable.fieldKey(), method, pjp.getArgs());


		Class<?> returnType = cacheable.classType();
		
		
		if(logger.isDebugEnabled()) {
			logger.debug("读取缓存中查找【"+ key +"】对应的值");
		}
		
		if(redisHashService.hasKey(key, fieldKey,returnType)){
			// 使用redis 的hash进行存取，易于管理
			result = redisHashService.get(key, fieldKey,returnType);
		}
		
		
		if (result == null) {
			try {
				if(logger.isDebugEnabled()) {
					logger.debug("从缓存中查找【"+ key +"】对应的"+fieldKey+"值失败，正在载入缓存.....");
				}
				result = pjp.proceed();
				Assert.notNull(fieldKey);
				redisHashService.set(key, fieldKey, result,returnType,cacheable.expireTime());
				
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
		return result;
	}
	
	@After("cacheEvictAspect()")
	public void doAfterCache(JoinPoint jpt){
		Boolean cacheEnable =staticConfigService.getBooleanValue(CACHE_ENABLE_KEY);
		if(!cacheEnable){
			return;
		}
		List<String> keys = getKey(jpt);
		if(logger.isDebugEnabled()) {
			logger.debug("从缓存中删除【"+ keys +"】对应的值");
		}
		redisHashService.del(keys);
	}

}
